package forum.server.controller;


import java.util.HashMap;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.Vector;
import java.util.regex.PatternSyntaxException;

import forum.server.domain.Post;
import forum.shared.exceptions.InvalidSearchRangeException;
import forum.shared.exceptions.NoSuchUserException;

public class BannanaSearch implements SearchEngine {

	private static BannanaSearch instance = null;
	private Map<String, Vector<SearchHit>> words_in_posts;
	private Map<String,Vector<SearchHit>> posts_by_user;
	private Map<Post, Vector<String>> post_words;
	
	public static BannanaSearch getInstance() {
		if (instance == null) {
			instance = new BannanaSearch();
		}
		return instance;
	}

	private BannanaSearch() {
		this.words_in_posts = new HashMap<String, Vector<SearchHit>>();
		this.posts_by_user = new HashMap<String, Vector<SearchHit>>();
		this.post_words = new HashMap<Post, Vector<String>>();
	}
	
	@Override
	public void removeData(Post p) {
		Vector<String> words = post_words.get(p);
		if (words != null) {
			for (String s: words) {
				words_in_posts.get(s).remove(new SearchHit(p,1));
			}
			post_words.remove(p);
		}
	}
	
	@Override
	public void addData(Post p) {
		String author = p.getAuthor();
		if (! posts_by_user.containsKey(author)) {
			posts_by_user.put(author,new Vector<SearchHit>());
		}
		posts_by_user.get(author).add(new SearchHit(p,1));
		if (p.getContent() == null) {
			return;
		}
		String phrase = p.getContent().getContent();
		//String[] words = phrase.split(" |\n|.");
		StringTokenizer st = new StringTokenizer(phrase);
		Vector<String> postWords = new Vector<String>();
		while (st.hasMoreTokens()) {
			String s = st.nextToken();
			postWords.add(s);
			if (! words_in_posts.containsKey(s)) {
				words_in_posts.put(s, new Vector<SearchHit>());
			}
			Vector<SearchHit> posts = words_in_posts.get(s);
			SearchHit sh = new SearchHit(p, countScore(s, phrase));
			posts.remove(sh);
			addInPlace(sh,posts);	
		}
		post_words.put(p, postWords);	
	}

	@Override
	public SearchHit[] searchByAuthor(String username, int from, int to) throws NoSuchUserException, InvalidSearchRangeException{
		if (to < from) {
			throw new InvalidSearchRangeException("cannot return "+(to-from)+" results");
		}
		Vector<SearchHit> posts = posts_by_user.get(username);
		if (posts == null) {
			throw new NoSuchUserException(username+" is not a valid username, or the user did not publish any posts.");
		}
		posts.setSize(Math.min(to - from + 1, posts.size()));
		SearchHit[] result = new SearchHit[posts.size()];
		posts.copyInto(result);
		return result;
	}

	
	@Override
	public SearchHit[] searchByContent(String phrase, int from, int to) throws InvalidSearchRangeException {
		if (to < from) {
			throw new InvalidSearchRangeException("cannot return "+(to-from)+" results");
		}
		Vector<SearchHit> result = new Vector<SearchHit>();
		Vector<SearchHit> ans = null;
		Vector<SearchHit> not_ans = null;
		//Debug
		System.out.println("Searching by OR");
		String[] or_phrases = phrase.split(" OR ");
		if (or_phrases == null || or_phrases.length == 0) {
			or_phrases = new String[1];
			or_phrases[0] = phrase;
		}
		for (String s: or_phrases) {
			//Debug
			System.out.println("Searching by AND");
			String[] s_and_phrases = s.split(" AND ");
			if (s_and_phrases == null || s_and_phrases.length == 0) {
				s_and_phrases = new String[1];
				s_and_phrases[0] = s;
			}
			ans = new Vector<SearchHit>();
			not_ans = new Vector<SearchHit>();
			for (String s_and_phrase: s_and_phrases) {
				//Debug
				System.out.println("s_and_phrase is: "+s_and_phrase);
				if (! this.appendPostsWhichHaveThePhrase(s_and_phrase, ans, not_ans)) {
					ans = null;
					break;
				}
			}

			this.vectorAppendNoDuplicates(result,ans);
			if (result.size() > (from - to + 1)) {
				break;
			}
			
		}
		result.setSize(Math.min(to - from + 1, result.size()));
		SearchHit[] toReturn = new SearchHit[result.size()];
		result.copyInto(toReturn);
		//Debug
		
		return toReturn;
	}
	
	/**
	 * Appends a duplicate free version of 'copyFrom' to 'toAppend'. 
	 * @param toAppend
	 * @param copyFrom
	 */
	private void vectorAppendNoDuplicates(Vector<SearchHit> toAppend, Vector<SearchHit> copyFrom) {
		if (copyFrom == null) {
			return;
		}
		for (SearchHit sh: copyFrom) {
			if (! toAppend.contains(sh)) {
				toAppend.add(sh);
			}
		}
	}
	
	/**
	 * Appends all the SearchHits which are related to posts that contain the phrase 'phrase'
	 * to the Vector 'toAppend', and all that don't to the Vector 'notToAppend'.
	 * @param phrase
	 * @param toAppend
	 * @param notToAppend
	 * @return true iff at least one SearchHit was appended to 'toAppend'.
	 */
	private boolean appendPostsWhichHaveThePhrase(String phrase, Vector<SearchHit> toAppend, Vector<SearchHit> notToAppend) {
		StringTokenizer st = new StringTokenizer(phrase);
		//String[] splitted_words = phrase.split(" |\n|.");
		if (!st.hasMoreTokens()) {
			//Debug
			System.out.println("No more Tokens!!");
			return false;
		}
		Vector<SearchHit> v = words_in_posts.get(st.nextToken());
		if (v == null || v.size()==0) {
			//Debug
			System.out.println("no post contains \""+phrase+"\"");
			return false;
		}
		for (SearchHit sh: v) {
			if (!notToAppend.contains(sh)) {
				if(sh.getPost().getContent()!=null && sh.getPost().getContent().getContent().contains(phrase)) {
					toAppend.add(new SearchHit(sh.getPost(), countScore(phrase, sh.getPost().getContent().getContent())));
					//Debug
					System.out.println("appended a searchHit with post #"+sh.getPost().getPostId()+" to the result");
				}
				else {
					notToAppend.add(sh);
					//Debug
					System.out.println("post #"+sh.getPost().getPostId()+"is not in the result");
				}
			}
			//Debug
			System.out.println("notToAppend contains a SearchHit with post #"+sh.getPost().getPostId());
		}
		return true;
	}
	
	/**
	 * Counts the number of occurrences of 'word' in 'phrase' 
	 * @param word
	 * @param phrase
	 * @return the ratio of the occurrences to in relation to the length of phrase.
	 */
	private double countScore(String word, String phrase) {
		double ans = 0;
		try {
			ans = ((double)phrase.split(word).length - 1)/phrase.length();
		}
		catch (PatternSyntaxException e) {
			//Should do something else with this error. not to give the highest score possible...
			return 1;
		}
		return ans;
	}
	
	/**
	 * The assumption on Vector v is that it's always sorted.
	 * thus, placing sh in the right hierarchical place will keep it sorted.
	 * @param sh the SearchHit element to insert.
	 * @param v the vector to insert 'sh' into.
	 */
	private void addInPlace(SearchHit sh, Vector<SearchHit> v) {
		if (v.size() == 0) {
			v.add(sh);
			return;
		}
		for (int i=0; i<v.size(); i++) {
			SearchHit shv = v.get(i);
			if (sh.getScore()<=shv.getScore()) {
				v.insertElementAt(sh,i);
				return;
			}
		}	
	}
}